"""
Layers - Layer 2
Stackable, byte-level wrappers over streams or other layers, providing things
like buffering and forwarding.
"""
from stream import closed_stream, SeekableMixin


class Layer(object):
    """the base abstract layer"""
    __slots__ = ["_substream"]
    
    def __init__(self, substream):
        object.__setattr__(self, "_substream", substream)
    def __repr__(self):
        return "<%s(%r)>" % (self.__class__.__name__, self._substream)
    
    def __getattr__(self, name):
        return getattr(self._substream, name)
    def __setattr__(self, name, value):
        setattr(self._substream, name, value)
    
    __members__ = property(lambda self: dir(self._substream))
    
    def close(self):
        self._substream.close()
        object.__setattr__(self, "_substream", closed_stream)
    closed = property(lambda self: self._substream is closed_stream)


class BufferingLayer(Layer, SeekableMixin):
    __slots__ = ["_bufsize", "_rbuf", "_wbuf", "_wbufsize"]
    DEFAULT_BUFFER_SIZE = 8100
    
    def __init__(self, substream, bufsize = None):
        Layer.__init__(self, substream)
        if bufsize is None:
            bufsize = self.DEFAULT_BUFFER_SIZE
        if bufsize <=0:
            raise ValueError("bufsize must be > 0")
        object.__setattr__(self, "_bufsize", bufsize)
        object.__setattr__(self, "_rbuf", "")
        object.__setattr__(self, "_wbuf", [])
        object.__setattr__(self, "_wbufsize", 0)
    
    def __del__(self):
        self.flush()
    
    def close(self):
        self.flush()
        Layer.close(self)
    
    def flush(self):
        if self._wbufsize:
            self._substream.write("".join(self._wbuf))
            object.__setattr__(self, "_wbuf", [])
            object.__setattr__(self, "_wbufsize", 0)
            if hasattr(self._substream, "sync"):
                self._substream.sync()
    
    def unread(self, data):
        """pushes the given data into the read buffer"""
        object.__setattr__(self, "_rbuf", data + self._rbuf)
    
    def peek(self, count):
        """a read operation that does not remove the data from the read 
        buffer"""
        data = self.read(count)
        self.unread(data)
        return data
    
    def read(self, count):
        l = len(self._rbuf)
        if count == 0:
            return ""
        elif count < 0:
            raise ValueError("`count` must be >= 0")
        elif count < l:
            data = self._rbuf[:count]
            object.__setattr__(self, "_rbuf", self._rbuf[count:])
            return data
        elif count == l:
            data = self._rbuf
            object.__setattr__(self, "_rbuf", "")
            return data
        else: # count > l
            data = self._rbuf
            try:
                buf = self._substream.read(self._bufsize)
            except EOFError:
                buf = ""
            object.__setattr__(self, "_rbuf", buf)
            if data:
                return data
            if self._rbuf:
                data = self._rbuf[:count]
                object.__setattr__(self, "_rbuf", self._rbuf[count:])
                return data
            raise EOFError
    
    def readall(self):
        rbuf = self._rbuf
        object.__setattr__(self, "_rbuf", "")
        return rbuf + self._substream.readall()
    
    def write(self, data):
        object.__setattr__(self, "_wbufsize", self._wbufsize + len(data))
        self._wbuf.append(data)
        if self._wbufsize >= self._bufsize:
            self.flush()
    
    #
    # seeking (invalidates the buffers)
    #
    def _invalidate(self):
        self.flush()
        object.__setattr__(self, "_rbuf", "")
    def seekto(self, offset):
        self._substream.seekto(offset)
        self._invalidate()
    def seekby(self, offset):
        self._substream.seekby(offset)
        self._invalidate()
    def seekend(self, offset):
        self._substream.seekend(offset)
        self._invalidate()
    def tell(self):
        return self._substream.tell()


class LineBufferingLayer(BufferingLayer):
    __slots__ = ["_marker", "_linesizehint"]
    
    def __init__(self, substream, bufsize = None, marker = "\n", linesizehint = 400):
        if linesizehint <= 0:
            raise ValueError("`linesizehint` must be > 0")
        BufferingLayer.__init__(self, substream, bufsize)
        object.__setattr__(self, "_marker", marker)
        object.__setattr__(self, "_linesizehint", linesizehint)
    
    def readline(self):
        data = ""
        starthint = 0
        markerlen = len(self._marker)
        while True:
            buf = self.read(self._linesizehint)
            data += buf
            markerpos = data.find(self._marker, starthint)
            starthint += len(buf) - markerlen
            if markerpos == -1:
                data.append(buf)
            else:
                upto = markerpos + markerlen
                data.append(buf[upto:])
                self.unread(buf[:upto])
                break
        return data
    
    def write(self, data):
        upto = data.rfind(self._marker)
        if upto == -1:
            BufferingLayer.write(self, data)
        elif upto == len(data) - 1:
            BufferingLayer.write(self, data)
            BufferingLayer.flush(self)
        else:
            upto += len(self._marker)
            BufferingLayer.write(self, data[:upto])
            BufferingLayer.flush(self)
            BufferingLayer.write(self, data[upto:])
    
    def writeline(self, data):
        BufferingLayer.write(self, data)
        BufferingLayer.write(self, self._marker)
        self.flush()


class ForwardingLayer(Layer):
    """duplicates ('forwards') all write() operations performed on the 
    underlying stream to a number of other streams. similar to the unix `tee`
    command line program"""
    __slots__ = ["_dupstreams"]
    
    def __init__(self, substream, *dupstreams):
        Layer.__init__(self, substream)
        object.__setattr__(self, "_dupstreams", dupstreams)
    
    def write(self, data):
        self._substream.write(data)
        for ds in self._dupstreams:
            ds.write(data)


class RecordLayer(Layer):
    """adds a version of 'read' that reads exactly 'count' bytes. 
    if less are available, raises EOFError and unread()s the partially read 
    data; at EOF raises EOFError.
    note that the underlying stream must support unreading"""
    __slots__ = []
    
    def readrec(self, count):
        if count == 0:
            return ""
        if count < 0:
            raise ValueError("`count` must be >= 0")
        
        data = []
        try:
            while count > 0:
                buf = self._substream.read(count)
                count -= len(buf)
                data.append(buf)
        except EOFError:
            self._substream.unread("".join(data))
            raise
        else:
            return "".join(data)


class ResyncLayer(Layer, SeekableMixin):
    """resynchronizes an encoded stream"""
    __slots__ = ["_decoder"]
    
    def __init__(self, substream, enconding):
        self._decoder = codecs.lookup(enconding).inrementaldecoder()
    
    def seekto(self, offset):
        self._substream.seekend(offset)
        self.resync()
    def seekby(self, offset):
        self._substream.seekend(offset)
        self.resync()
    def seekend(self, offset):
        self._substream.seekend(offset)
        self.resync()
    
    def resync(self):
        self._decoder.reset()
        while True:
            b = self.read(1)
            try:
                data = self._decoder.decode(b)
            except:
                self._decoder.reset()
            else:
                if data:
                    self.unread(data)
                    break
        






    
    











